/*
 Xern Entity Relocation Network
 Copyright (C) 2010-2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include <stdio.h>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
#include "Xern/IResourceLoadedCallback.hpp"
#include "Xern/Entity.hpp"
#include "Xern/Engine.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/FilterStream.hpp"
#include "Xern/ResourceManager.hpp"
#include "Source.hpp"

namespace XernAL
{
	// SamplerLoadedCallback
	class SamplerLoadedCallback: public IResourceLoadedCallback
	{
	public:
		SamplerLoadedCallback(boost::shared_ptr<Source> source);
		~SamplerLoadedCallback();

		virtual void OnLoadedResource(boost::shared_ptr<IResource> resource);

	private:
		boost::shared_ptr<Source> source;
	};

	SamplerLoadedCallback::SamplerLoadedCallback(boost::shared_ptr<Source> source)
		: source(source)
	{
	}

	SamplerLoadedCallback::~SamplerLoadedCallback()
	{
	}

	void SamplerLoadedCallback::OnLoadedResource(boost::shared_ptr<IResource> resource)
	{
		// Check that the resource is actually a sampler.
		boost::shared_ptr<ISoundSampler> sampler = boost::dynamic_pointer_cast<ISoundSampler> (resource);
		if(!sampler)
			throw XernException("Expected sound sampler.");

		// Set the sampler.
		source->SetSampler(sampler);
	}

	// Source implementation.
	Source::Source(boost::shared_ptr<SoundCache> soundCache)
		: soundCache(soundCache), currentState(SS_STOPPED)
	{
		if(!soundCache->IsNoSound())
		{
			alGenSources(1, &source);

			// Set default parameters.
			alSourcef(source, AL_PITCH, 1.0f);
			alSourcef(source, AL_GAIN, 1.0f);
			alSourcei(source, AL_LOOPING, AL_FALSE);
		}

		// Set to null the stream buffers.
		streamBuffers[0] = streamBuffers[1] = 0;
		streamBuffer = NULL;
		streamBufferSize = 0;
		streamFinish = false;
		globalSource = false;
		id = nil_uuid();
	}

	Source::~Source()
	{
		if(!soundCache->IsNoSound())
		{
			alDeleteSources(1, &source);
			delete [] streamBuffer;
		}
	}

	IResource::ResourceFlags Source::GetFlags() const
	{
		return ResourceFlags(RFL_MOVEMENT_LISTENER | RFL_SERIALIZABLE);
	}

	void Source::UpdateState(boost::shared_ptr<IDataFilter> stateData)
	{
	}

	const uuid &Source::GetId() const
	{
		return this->id;
	}

	void Source::SetId(const uuid &id)
	{
		this->id = id;
	}

	const char *Source::GetMimeType() const
	{
		return "application/vnd.xern.sound.source";
	}

	const char *Source::GetFileExtension() const
	{
		return "";
	}

	bool Source::IsFileSerializable() const
	{
		return false;
	}

	bool Source::IsMemorySerializable() const
	{
		return true;
	}

	boost::shared_ptr<IDataFilter> Source::GetSerializedData() const
	{
		// Create the source data.
		JsonObject data;

		// Write the state.
		switch(currentState)
		{
		case SS_PLAYING:
			data["state"] = "play";
			break;
		case SS_PAUSED:
			data["state"] = "pause";
			break;
		case SS_STOPPED:
		default:
			data["state"] = "stop";
			break;
		}

		// Send the sound sampler.
		if(sampler)
			data["sampler"] = boost::lexical_cast<std::string> (sampler->GetId());
		else
			data["sampler"] = JsonNull();

		// Send the source flags.
		data["loop"] = looping;
		data["global"] = globalSource;

		// Serialize the data.
		boost::shared_ptr<StringFilter> filter(new StringFilter());
		filter->GetStream() << data;

		return filter;
	}

	ISoundSource::SourceState Source::GetState() const
	{
		return currentState;
	}

	void Source::SetState(SourceState state)
	{
		if(currentState == state)
			return;

		currentState = state;

		if(soundCache->IsNoSound())
			return;

		// Update the source state.
		switch(state)
		{
		default:
		case SS_STOPPED:
			alSourceStop(source);
			break;
		case SS_PAUSED:
			alSourcePause(source);
			break;
		case SS_PLAYING:
			alSourcePlay(source);
			break;
		}
	}

	bool Source::IsLooping() const
	{
		return looping;
	}

	void Source::SetLooping(bool loop)
	{
		looping = loop;

		if(soundCache->IsNoSound())
			return;

		// Update the source looping.
		if(!sampler || !sampler->IsStreamed())
			alSourcei(source, AL_LOOPING, loop ? AL_TRUE : AL_FALSE);
	}

	bool Source::IsGlobalSource() const
	{
		return globalSource;
	}

	void Source::SetGlobalSource(bool value)
	{
		globalSource = value;

		if(soundCache->IsNoSound())
			return;

		// Modify the source parameters.
		if(globalSource)
		{
		    alSource3f(source, AL_POSITION, 0.0, 0.0, 0.0);
		    alSource3f(source, AL_VELOCITY, 0.0, 0.0, 0.0);
		    alSource3f(source, AL_DIRECTION, 0.0, 0.0, 0.0);
		    alSourcef(source, AL_ROLLOFF_FACTOR, 0.0);
		    alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
		}
		else
		{
		    alSourcef(source, AL_ROLLOFF_FACTOR, 1.0);
		    alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
		}
	}

	boost::shared_ptr<ISoundSampler> Source::GetSampler() const
	{
		return sampler;
	}

	void Source::SetSampler(boost::shared_ptr<ISoundSampler> sampler)
	{
		if(this->sampler == sampler)
			return;

		this->sampler = sampler;

		if(soundCache->IsNoSound())
			return;

		// Restart the playing sources.
		bool restart = currentState == SS_PLAYING;
		if(restart)
			alSourceStop(source);

		PrepareBuffers();

		// Play with the new sound.
		if(restart)
			alSourcePlay(source);
	}

	void Source::OnMove(boost::shared_ptr<Entity> entity)
	{
		if(entity && !globalSource)
		{
			// Get the parent position and velocity.
			const ReversibleTransform &transform = entity->GetWorldTransform();
			const Vector3 &position = transform.GetVector();
			const Vector3 &velocity = entity->GetLinearVelocity();

			// Update them.
			if(!soundCache->IsNoSound())
			{
				alSource3f(source, AL_POSITION, position.x, position.y, position.z);
				alSource3f(source, AL_VELOCITY, velocity.x, velocity.y, velocity.z);
			}
		}
	}

	void Source::Frame(float delta)
	{
		if(!sampler)
			return;

		// Send the next buffer.
		StreamBuffer();
	}

	void Source::PrepareBuffers()
	{
		// Don't prepare without sound.
		if(soundCache->IsNoSound())
			return;

		// Remove the reference into the old sampler.
		if(!sampler || sampler->IsStreamed())
			sampleBuffer.reset();

		if(!sampler)
		{
			// Set a null buffer.
			alSourcei(source, AL_BUFFER, 0);

			// Delete the stream buffers.
			if(streamBuffer)
			{
				alDeleteBuffers(2, streamBuffers);
				delete [] streamBuffer;
				streamBuffer = NULL;
				streamBufferSize = 0;
			}
			return;
		}

		// Streamed samplers are handled differently.
		if(sampler->IsStreamed())
		{
			// Create the buffers for streaming.
			if(!streamBuffer)
				alGenBuffers(2, streamBuffers);

			// Read the format.
			int numchannels = sampler->GetChannels();
			int sampleSize = sampler->GetSampleSize();
			if(numchannels == 1)
			{
				if(sampleSize == 1)
					format = AL_FORMAT_MONO8;
				else if(sampleSize == 2)
					format = AL_FORMAT_MONO16;
				else
					throw XernException("Unsupported sample size.");
			}
			else if(numchannels == 2)
			{
				if(sampleSize == 1)
					format = AL_FORMAT_STEREO8;
				else if(sampleSize == 2)
					format = AL_FORMAT_STEREO16;
				else
					throw XernException("Unsupported sample size.");
			}
			else
				throw XernException("Only mono or stereo sound is supported.");

			// Make a 40 ms buffer.
			const size_t bufferDiv = 1000 /*s*/ / 40 /*ms*/;
			size_t newBufferSize = (numchannels*sampleSize*sampler->GetSampleRate())/bufferDiv;

			// Recreate the buffer, if needed.
			if(newBufferSize != streamBufferSize)
			{
				delete [] streamBuffer;
				streamBuffer = new char[newBufferSize];
				streamBufferSize = newBufferSize;
			}

			// Reset the finish flag.
			streamFinish = false;

			// Disable looping.
			alSourcei(source, AL_LOOPING, AL_FALSE);

			// Start the streaming.
			StreamStart();
		}
		else
		{
			// Cache the sound.
			sampleBuffer = soundCache->CacheSound(sampler);

			// Set the source buffer.
			alSourcei(source, AL_BUFFER, sampleBuffer->GetBuffer());

			// Delete the stream buffers.
			if(streamBuffer)
			{
				alDeleteBuffers(2, streamBuffers);
				delete [] streamBuffer;
				streamBuffer = NULL;
				streamBufferSize = 0;
			}
		}

		// Set global source for two channels samplers.
		if(sampler && sampler->GetChannels() > 1)
			SetGlobalSource(true);
	}

	void Source::StreamStart()
	{
		// Read and queue the buffers.
		if(ReadBuffer(streamBuffers[0]))
		{
			alSourceQueueBuffers(source, 1, &streamBuffers[0]);
			if(ReadBuffer(streamBuffers[1]))
				alSourceQueueBuffers(source, 1, &streamBuffers[1]);
		}

		if(currentState == SS_PLAYING)
			alSourcePlay(source);
	}

	bool Source::ReadBuffer(ALuint buffer)
	{
		// Read the buffer.
		size_t readed = sampler->Read(streamBufferSize, streamBuffer);
		if(readed == 0)
		{
			if(looping)
			{
				sampler->Rewind();
				readed = sampler->Read(streamBufferSize, streamBuffer);
				if(readed == 0)
					return false;
			}
			else
			{
				streamFinish = true;
				return false;
			}
		}

		alBufferData(buffer, format, streamBuffer, readed, sampler->GetSampleRate());
		return true;
	}

	void Source::StreamBuffer()
	{
		// Ignore without sound.
		if(soundCache->IsNoSound())
			return;

		// Finish streaming.
		if(streamFinish)
			return;

		// Do nothing with static samplers.
		if(!sampler || !sampler->IsStreamed())
			return;

		// Check the sound state.
		if(currentState != SS_PLAYING)
			return;

		// Read the number of buffer processed.
		int processed;
		alGetSourceiv(source, AL_BUFFERS_PROCESSED, &processed);

		// Fill all of the buffers needed.
		while(processed--)
		{
			// Unqueue the buffer.
			ALuint buffer;
			alSourceUnqueueBuffers(source, 1, &buffer);

			// Fill the buffer.
			if(!ReadBuffer(buffer))
				break;

			// Send the buffer.
			alSourceQueueBuffers(source, 1, &buffer);
		}

		// Continue playing if needed.
		if(currentState == SS_PLAYING)
		{
			int state;
			alGetSourcei(source, AL_SOURCE_STATE, &state);
			if(state != AL_PLAYING)
				alSourcePlay(source);
		}
	}

	void Source::LoadData(const JsonObject &data)
	{
		const JsonValue &samplerName = data["sampler"];
		if(!JsonIsNull(samplerName))
		{
			// Parse the sampler id.
			uuid samplerId = boost::lexical_cast<uuid> (boost::get<JsonString> (samplerName).string());

			// Load the sampler.
			boost::shared_ptr<SamplerLoadedCallback> callback(new SamplerLoadedCallback(shared_from_this()));
			Engine::Get().GetResourceManager()->DelayedLoad(samplerId, callback);
		}

		// Read the state.
		JsonString state = boost::get<JsonString> (data["state"]);
		if(state == "play")
			SetState(SS_PLAYING);
		else if(state == "pause")
			SetState(SS_PAUSED);
		else // if state == "stop"
			SetState(SS_STOPPED);

		// Read the flags.
		SetLooping(boost::get<JsonNumber> (data["loop"]));
		SetGlobalSource(boost::get<JsonNumber> (data["global"]));
	}

}; // namespace XernAL
